package br.edu.ufcg.msnlab2.grupo07;

import java.util.HashMap;
import java.util.Map;

import junit.framework.TestCase;
import mathExpr.parser.javacc.ParseException;
import br.edu.ufcg.msnlab2.InvalidFunctionException;
import br.edu.ufcg.msnlab2.MSNLabException;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.MiserMonteCarloSolverImpl;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.MonteCarloResult;
import br.edu.ufcg.msnlab2.grupo07.monteCarlo.Tuple;
import br.edu.ufcg.msnlab2.misc.Function;
import br.edu.ufcg.msnlab2.misc.FunctionImpl;

/**
 * This class contains tests for the implementation of the MISER Monte
 * Carlo algorithm to estimate integral values.
 * @author David Candeia
 * @author Felipe Leal
 *
 */
public class MiserMonteCarloTest extends TestCase{
	
	private MiserMonteCarloSolverImpl miserSolver;
	
	public void setUp(){
		miserSolver = new MiserMonteCarloSolverImpl();
		assertNotNull(miserSolver);
	}
	
	/**
	 * This method verifies the impact of changing the dithering parameter.
	 * As dithering is changed the point chosen in each dimension to bisect the volume
	 * changes.
	 * @throws MSNLabException
	 */
	public void testDifferentDithering() throws MSNLabException{
		
		//Lower Bound. CORRECT VALUE: 0.3005
		Function function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");
		
		Map<String, Tuple<Double, Double>> limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		MonteCarloResult result = miserSolver.solve(function, limits, 1000000, 0.0);
		assertEquals(result.getIntegrationResult(), 0.29515384289055885, 0.01);
		assertEquals(result.getError(), 5.7212150655288026E-9);
		
		//Upper bound
		function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		result = miserSolver.solve(function, limits, 1000000, 0.5);
		assertEquals(result.getIntegrationResult(), 0.19537647439598804, 0.01);
		assertEquals(result.getError(), 0.001087143313864369);
		
		//Value between bounds
		function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		result = miserSolver.solve(function, limits, 1000000, 0.277);
		assertEquals(result.getIntegrationResult(), 0.2911163781837637, 0.01);
		assertEquals(result.getError(), 1.2292960391274533E-7, 0.01);
	}
	
	/**
	 * This method verifies if the implementation of the MISER algorithm is 
	 * computing correctly the integral estimations and its errors. Only 
	 * functions with one variable are tested.
	 * @throws ParseException 
	 * @throws MSNLabException
	 */
	public void testMiserSolverForOneVariable() throws ParseException, MSNLabException{
		//Calculating PI		
		Function function = new FunctionImpl("(1-x^2)^(1/2)");
		
		Map<String, Tuple<Double, Double>> limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(-1.0d, 1.0d));
		
		MonteCarloResult result = miserSolver.solve(function, limits, 1000);
		Double pi = 2 * result.getIntegrationResult();
		assertEquals(pi, Math.PI, 0.01);
		assertEquals(result.getError(), 2.2208616239020727E-6);
		
		//Another function. Correct value: 2516.8056708970594
		function = new FunctionImpl("x^3 + 1/x + x^(1/2)");
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(2.0d, 10.0d));
		
		result = miserSolver.solve(function, limits, 100);
		assertEquals(result.getIntegrationResult(), 2131.7606115481917, 0.03);
		assertEquals(result.getError(), 2026.1817949638034);
		
		//More points enhance the estimative.
		function = new FunctionImpl("x^3 + 1/x + x^(1/2)");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(2.0d, 10.0d));
		
		result = miserSolver.solve(function, limits, 10000);
		assertEquals(result.getIntegrationResult(), 2510.9015657000423, 0.03);
		assertEquals(result.getError(), 0.009200885939069245);
		
		//Another function. Correct value: 0.3865106039343533
		function = new FunctionImpl("sin(cos(x))");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(5.0d, 10.0d));
		
		result = miserSolver.solve(function, limits, 100);
		assertEquals(result.getIntegrationResult(), 0.6894248641863042, 0.00001);
		assertEquals(result.getError(), 0.004560376994075994);
		
		//Increasing sampled points. Correct value: 0.3865106039343533
		function = new FunctionImpl("sin(cos(x))");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(5.0d, 10.0d));
		
		result = miserSolver.solve(function, limits, 1000);
		assertEquals(result.getIntegrationResult(), 0.40104093576500605, 0.00001);
		assertEquals(result.getError(), 1.4750573073979148E-5);
		
		//Using an invalid function expression
		function = new FunctionImpl("3.14*x*((x)^(1/2))");
		
		try{
			miserSolver.solve(null, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		//Invalid limits
		try{
			miserSolver.solve(function, null, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			miserSolver.solve(function, new HashMap<String, Tuple<Double,Double>>(), 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("h", new Tuple<Double, Double>(0.0d, 1.0d));
		try{
			miserSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			miserSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
	}
	
	/**
	 * This method verifies if the implementation of the MISER algorithm is 
	 * computing correctly the integral estimations and its errors. Only 
	 * functions with two or more variables are tested.
	 * @throws ParseException 
	 * @throws MSNLabException
	 */
	public void testMiserSolverForTwoOrMoreVariables() throws ParseException, MSNLabException{
		//Test with two variables. More points improve the estimation and decreases the error! CORRECT VALUE: 0.30023		
		Function function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");
		
		Map<String, Tuple<Double,Double>> limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		MonteCarloResult result2 = miserSolver.solve(function, limits, 1000000);
		assertEquals(result2.getIntegrationResult(), 0.29515384289055885, 0.01);
		assertEquals(result2.getError(), 5.7212150655288026E-9);
		
		//Very small number of points. Large error and bad estimative
		result2 = miserSolver.solve(function, limits, 10);
		assertEquals(result2.getIntegrationResult(), 0.20769311736031612, 0.01);
		assertEquals(result2.getError(), 0.003702035658590103);
		
		//Another function. Correct value: 1220.2627703681487
		function = new FunctionImpl("sin(x)+cos(1/y)");
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(-1.0d, 10.0d));
		limits.put("y", new Tuple<Double, Double>(1.0d, 100.0d));
		
		result2 = miserSolver.solve(function, limits, 80);
		assertEquals(result2.getIntegrationResult(), 1091.2345729559493, 0.01);
		assertEquals(result2.getError(), 6.411255915560678);
		
		//Another function. CORRECT VALUE: 1.3542694652310872		
		function = new FunctionImpl("sin(cos(x)) + (sin(y))/(cos(y))");
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		result2 = miserSolver.solve(function, limits, 100);
		assertEquals(result2.getIntegrationResult(), 1.2576163683915194, 0.01);
		assertEquals(result2.getError(), 0.0021861628501439875);
		
		//Increasing number of points. CORRECT VALUE: 1.3542694652310872		
		function = new FunctionImpl("sin(cos(x)) + (sin(y))/(cos(y))");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(0.0d, 1.0d));
		limits.put("y", new Tuple<Double, Double>(0.0d, 1.0d));
		
		result2 = miserSolver.solve(function, limits, 5000);
		assertEquals(result2.getIntegrationResult(), 1.3051782166856278, 0.01);
		assertEquals(result2.getError(), 1.3986898805554157E-5);

		//Another function with exponentials. CORRECT VALUE: 1.9983513245889003*^30
		function = new FunctionImpl("3^(x*y) + 10*x - 1/y");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(3.0d, 7.5d));
		limits.put("y", new Tuple<Double, Double>(2.0d, 9.0d));
		
		result2 = miserSolver.solve(function, limits, 400);
		assertEquals(result2.getIntegrationResult(), 1.3238827795139473E30, 0.01);
		assertEquals(result2.getError(), 2.2875650219221598E57);
		
		//Increasing number of points. CORRECT VALUE: 1.9983513245889003*^30
		function = new FunctionImpl("3^(x*y) + 10*x - 1/y");

		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("x", new Tuple<Double, Double>(3.0d, 7.5d));
		limits.put("y", new Tuple<Double, Double>(2.0d, 9.0d));
		
		result2 = miserSolver.solve(function, limits, 8000);
		assertEquals(result2.getIntegrationResult(), 8.59288383664526E29, 0.01);
		assertEquals(result2.getError(), 1.4641695523518927E57);
		
		//Using an invalid function expression
		function = new FunctionImpl("sin(3.14*x*y)*((x*y)^(1/2))");
		
		try{
			miserSolver.solve(null, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		//Invalid limits
		try{
			miserSolver.solve(function, null, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			miserSolver.solve(function, new HashMap<String, Tuple<Double,Double>>(), 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		limits = new HashMap<String, Tuple<Double,Double>>();
		limits.put("h", new Tuple<Double, Double>(0.0d, 1.0d));
		try{
			miserSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
		
		try{
			miserSolver.solve(function, limits, 10);
			fail("Invalid expression!");
		}catch(RuntimeException e){
		}
	}
}
